﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Síť;

namespace NetShips {

    /// <summary>
    /// Main window.
    /// </summary>
    public partial class Form1: Form {

        private System.Timers.Timer Synchronization;

        private int frmWidth;               // for ensuring constant width
        private bool CheckedByCode = false; // true if button's checked state was changed from program
        private delegate void dClose();

        private const string PRE_GAME = "GAME";
        private const string PRE_NAME = "NAME";
        private const string PRE_ATTACK = "ATAK";
        private const string PRE_HIT = "RESU";
        private const string PRE_SHIP = "LODE";
        private const string PRE_CHAT = "CHAT";

        private enum States_Enum {
            Planning,
            ReadyForPlay,
            Play,
            GameOver
        }
        private struct State {

#if DEBUG
            public const int MAX_SHIPS = 1;
#else
            public const int MAX_SHIPS = 10;
#endif
            public int ShipsCount;
            public Boolean OpponentReady;
            public Boolean MyTurn;
            public States_Enum GameState;
        }

        private struct TShoot {
            public Point Target;
        }

        private State _state = new State();
        private TShoot Shot;

        /// <summary>
        /// Reference to actually pressed button.
        /// </summary>
        private ToolStripButton ActBtn;

        private Communicator _Com;

        /// <summary>
        /// Reference to actualy managed ship.
        /// </summary>
        private Ship ActShip;

        /// <summary>
        /// Position mouse is on.
        /// </summary>
        private Point mouseIndex;


        /// <summary>
        /// Window constructor, prepares window.
        /// </summary>
        public Form1(Communicator Kom) {
            _Com = Kom;
            _Com.MessageArrived += new Communicator.dMessageArrived(_Com_MessageArrived);
            _Com.Disconnected += new Communicator.dDisconnected(_Com_Disconnected);
            InitializeComponent();

            // after game restart windows ned not to appear together
            // so I am starting Timer which have to bomb oponent with my nick
            // until I receive his nick 
            // TODO: it occured to me, that nick can't change during restart, so 
            // sending nick again shouldn't be necessary...
            Synchronization = new System.Timers.Timer(200);
            Synchronization.Elapsed += new System.Timers.ElapsedEventHandler(Synchronization_Elapsed);
            Synchronization.Start();

            this.lblPlayer1.Text = Program.Names[0];
            this.lblPlayer2.Text = "";

            frmWidth = this.Width;
        }

        void Synchronization_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
            _Com.Write(PRE_NAME + Program.Names[0]);
        }

        // oponent disconnected
        void _Com_Disconnected(object source, EventArgs e) {
            if(_state.GameState != States_Enum.GameOver) {
                MessageBox.Show("Opponent disconnected!\nGame will end.", "NetShips", MessageBoxButtons.OK, MessageBoxIcon.Stop);
                CancelEventHnd();
                if(this.InvokeRequired) {
                    dClose d = new dClose(Close);
                    this.Invoke(d, new object[] { });
                    Application.ExitThread();
                }
                else {
                    this.Close();
                }
            }
        }

        void _Com_MessageArrived(object source, MessageArrivedEventArgs e) {
            int X, Y;
            string msg2test = e.Message.Substring(0, 4);
            switch(msg2test) {
                case PRE_CHAT:
                    // CHAT.?
                    Chat_ReceivedMsg(e.Message.Substring(4));
                    break;
                case PRE_NAME:
                    // NAME.?
                    if(Synchronization.Enabled) {
                        Synchronization.Close();
                        // I send the name once more just in case.
                        // Sometimes become situation, when it is necessary.
                        _Com.Write(PRE_NAME + Program.Names[0]);
                    }

                    Program.Names[1] = e.Message.Substring(4);
                    lblPlayer2.Text = Program.Names[1];
                    break;
                case PRE_ATTACK:
                    // ATAKxxyy
                    X = int.Parse(e.Message.Substring(4, 2));
                    Y = int.Parse(e.Message.Substring(6, 2));

                    if(myField.Shoot(new Point(X, Y)) == FieldPoint.FieldPointType_ENUM.Sea) {
                        // Opponent missed :P It's my turn...
                        stlStatus.Text = "It's your turn!";
                        _state.MyTurn = true;
                        opponentsField.Enabled = true;
                        this.Activate();
                    }
                    myField.Invalidate();

                    if(myField.Ships.CountNotDown == 0) {
                        _state.GameState = States_Enum.GameOver;
                        opponentsField.ShowAllShips();
                        if(MessageBox.Show(Program.Names[1] + " defeated you!! :-P\nPlay for retaliation?", "NetShips", MessageBoxButtons.YesNo, MessageBoxIcon.Stop) == DialogResult.Yes) {
                            Restart();
                        }
                        else {
                            End();
                        }
                    }
                    break;
                case PRE_HIT:

                    break;
                case PRE_GAME:
                    _state.OpponentReady = true;
                    stlStatus.Text = "Opponent is waiting...";

                    if(_state.GameState == States_Enum.ReadyForPlay) {
                        _state.GameState = States_Enum.Play;
                        stlStatus.Text = "It's your turn!";
                        opponentsField.Enabled = true;
                    }

                    break;
                case PRE_SHIP:
                    // oponent is sending list of his ships
                    // format: LODEtxxyyu
                    // t - type, [xx, yy] - position, u - direction
                    Ship.ShipTypes_ENUM typ;
                    Ship.Directions_ENUM uhel;
                    Ship l;

                    typ = (Ship.ShipTypes_ENUM)(int.Parse(e.Message.Substring(4, 1)));
                    X = int.Parse(e.Message.Substring(5, 2));
                    Y = int.Parse(e.Message.Substring(7, 2));
                    uhel = (Ship.Directions_ENUM)int.Parse(e.Message.Substring(9, 1));

                    l = opponentsField.Ships.Add(typ, new Point(X, Y), uhel);
                    l.Direction = uhel;
                    l.Visibility = false;

                    opponentsField.Invalidate();

                    break;
                default:
                    MessageBox.Show("I have received unknown data:" + e.Message);
                    break;
            }
        }

        private void Form1_Load(object sender, EventArgs e) {
            _state.GameState = States_Enum.Planning;
            _state.MyTurn = false;
            _state.ShipsCount = 0;

        }

        private void myField_Paint(object sender, PaintEventArgs e) {
            myField.Draw(e.Graphics);
        }

        private void opponent_Paint(object sender, PaintEventArgs e) {
            opponentsField.Draw(e.Graphics);
        }



        private void myField_MouseMove(object sender, MouseEventArgs e) {
            if(_state.GameState == States_Enum.Planning) {
                
                Point Index;
                Index = myField.GetIndex(e.X, e.Y);
                if(Index != mouseIndex) {
                    mouseIndex = Index;
                    if(ActShip != null) {
                        ActShip.Pozice = Index;
                        myField.Invalidate();
                    }
                }
            }
        }

        private void Form1_KeyUp(object sender, KeyEventArgs e) {
            if(e.KeyCode == System.Windows.Forms.Keys.Escape) {
                if(ActBtn != null) {
                    CheckedByCode = true;
                    ActBtn.Checked = false;
                    ActBtn = null;
                }
                if(ActShip != null) {
                    myField.Ships.Remove(ActShip);
                    ActShip = null;
                    myField.Invalidate();
                }
            }
            if(e.KeyCode == Keys.Enter) {
                Chat_SendMsg(txtMsg.Text);
                txtMsg.Text = "";
            }
        }

        private void myField_MouseClick(object sender, MouseEventArgs e) {
            if(_state.GameState == States_Enum.Planning) {
                switch(e.Button) {
                    case MouseButtons.Left:
                        Point pos = myField.GetIndex(e.X, e.Y);
                        if(ActShip == null) {
                            // there's no active ship on cursor
                            // i try to pick one sitting under the cursor
                            if((ActShip = myField.Ships.Get_Reference(myField.Points[pos.X, pos.Y])) == null)
                                return;

                            switch(ActShip.Type) {
                                case Ship.ShipTypes_ENUM.L:
                                    AddShip(tsbL);
                                    break;
                                case Ship.ShipTypes_ENUM.I:
                                    AddShip(tsbI);
                                    break;
                                case Ship.ShipTypes_ENUM.Battleship:
                                    AddShip(tsbBattleShip);
                                    break;
                                case Ship.ShipTypes_ENUM.Small:
                                    AddShip(tsbSmall);
                                    break;
                                case Ship.ShipTypes_ENUM.T:
                                    AddShip(tsbT);
                                    break;
                            }
                            _state.ShipsCount -= 1;
                            btnReady.Enabled = false;
                        }
                        else {
                            if(ActShip.FieldPointsTypes == FieldPoint.FieldPointType_ENUM.Ship) {

                                ActBtn.Text = Convert.ToString(Convert.ToInt32(ActBtn.Text) - 1);
                                if(ActBtn.Text == "0")
                                    ActBtn.Enabled = false;

                                ActShip = null;
                                CheckedByCode = true;
                                ActBtn.Checked = false;

                                ActBtn = null;

                                _state.ShipsCount += 1;
                                if(_state.ShipsCount >= State.MAX_SHIPS)
                                    btnReady.Enabled = true;
                            }

                        }

                        break;
                    case MouseButtons.Right:
                        if(ActShip != null) {
                            ActShip.Direction += 1;
                            myField.Invalidate();
                        }
                        break;
                }
            }
        }

        private void AddShip(ToolStripButton btn) {
            ActBtn = btn;
            ActBtn.Enabled = true;
            ActBtn.Text = Convert.ToString(Convert.ToInt32(ActBtn.Text) + 1);
            CheckedByCode = true;
            ActBtn.Checked = true;
        }

        private void opponent_MouseClick(object sender, MouseEventArgs e) {
            //int res;
            if(_state.GameState == States_Enum.Play) {
                if(_state.MyTurn) {
                    Point index;

                    index = opponentsField.GetIndex(e.X, e.Y);

                    switch(opponentsField.Shoot(index)) {
                        case FieldPoint.FieldPointType_ENUM.Sea:

                            // missed, it's opponent's turn
                            _state.MyTurn = false;
                            opponentsField.Enabled = false;
                            ShowShipsCount();

                            break;
                        case FieldPoint.FieldPointType_ENUM.Ship:
                            break;
                        default:  // place where i've already shot
                            return;
                    }

                    _Com.Write(PRE_ATTACK + FormatNum(index.X, 2) + FormatNum(index.Y, 2));

                    opponentsField.Invalidate();

                    if(opponentsField.Ships.CountNotDown == 0) {
                        _state.GameState = States_Enum.GameOver;
                        if(MessageBox.Show("You have defeated " + Program.Names[1] + "!!\n\nDo you want to stamp your victory?", "LANLodě", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) == DialogResult.Yes) {
                            Restart();
                            int test = 1;
                        }
                        else {
                            End();
                            int test = 1;
                        }
                    }
                }
            }
        }

        private void ShowShipsCount() {
            stlStatus.Text = "Remains " + opponentsField.Ships.CountNotDown + " ships to shoot.";
        }

        private string FormatNum(int source, int FinalLength) {
            string final;
            string s;
            string Matrix = new string('0', FinalLength);

            s = Convert.ToString(source);
            final = Matrix.Substring(0, FinalLength - s.Length) + s;

            return final;
        }

        private void btnReady_Click(object sender, EventArgs e) {
            // sending informations about my ships
            foreach(Ship l in myField.Ships) {
                _Com.Write(PRE_SHIP + (int)l.Type + FormatNum(l.Pozice.X, 2) + FormatNum(l.Pozice.Y, 2) + (int)l.Direction);
            }
            _Com.Write(PRE_GAME);
            _state.GameState = States_Enum.ReadyForPlay;
            if(_state.OpponentReady) {
                _state.MyTurn = false;
                _state.GameState = States_Enum.Play;
                ShowShipsCount();
            }
            else {
                _state.MyTurn = true;
                stlStatus.Text = "Waiting for oponent...";
            }
            btnReady.Visible = false;
            myField.Enabled = false;
            tstShipTypes.Enabled = false;

        }
        private void Restart() {
            Program.Retaliation = true;

            CancelEventHnd();
            this.Close();

            //}
        }

        void End() {
            Program.Retaliation = false;
            CancelEventHnd();
            this.Close();
        }

        private void CancelEventHnd() {
            _Com.Disconnected -= _Com_Disconnected;
            _Com.MessageArrived -= _Com_MessageArrived;
        }

        #region "Ship buttons handlers"

        private void CancelShipAdding() {
            if(ActShip != null) {
                myField.Ships.Remove(ActShip);
                myField.Invalidate();
                ActShip = null;
            }
        }

        private void tsbL_CheckedChanged(object sender, EventArgs e) {
            if(!CheckedByCode)
                if(tsbL.Checked) {
                    if(ActShip != null) {
                        ActBtn.Checked = false;
                        myField.Ships.Remove(ActShip);
                        myField.Invalidate();
                    }
                    ActBtn = tsbL;
                    ActShip = myField.Ships.Add(Ship.ShipTypes_ENUM.L, new Point());
                    ActShip.Visibility = true;

                }
                else
                    CancelShipAdding();
            else
                CheckedByCode = false;

        }

        private void tsbT_CheckedChanged(object sender, EventArgs e) {
            if(!CheckedByCode)
                if(tsbT.Checked) {
                    if(ActShip != null) {
                        ActBtn.Checked = false;
                        myField.Ships.Remove(ActShip);
                        myField.Invalidate();
                    }
                    ActBtn = tsbT;
                    ActShip = myField.Ships.Add(Ship.ShipTypes_ENUM.T, new Point());
                    ActShip.Visibility = true;
                }
                else
                    CancelShipAdding();
            else
                CheckedByCode = false;
        }

        private void tsbBattleShip_CheckedChanged(object sender, EventArgs e) {
            if(!CheckedByCode)
                if(tsbBattleShip.Checked) {
                    if(ActShip != null) {
                        ActBtn.Checked = false;
                        myField.Ships.Remove(ActShip);
                        myField.Invalidate();
                    }
                    ActBtn = tsbBattleShip;
                    ActShip = myField.Ships.Add(Ship.ShipTypes_ENUM.Battleship, new Point());
                    ActShip.Visibility = true;
                }
                else
                    CancelShipAdding();
            else
                CheckedByCode = false;
        }

        private void tsbI_CheckedChanged(object sender, EventArgs e) {
            if(!CheckedByCode)
                if(tsbI.Checked) {
                    if(ActShip != null) {
                        ActBtn.Checked = false;
                        myField.Ships.Remove(ActShip);
                        myField.Invalidate();
                    }
                    ActBtn = tsbI;
                    ActShip = myField.Ships.Add(Ship.ShipTypes_ENUM.I, new Point());
                    ActShip.Visibility = true;
                }
                else
                    CancelShipAdding();
            else
                CheckedByCode = false;
        }

        private void tsbSmall_CheckedChanged(object sender, EventArgs e) {
            if(!CheckedByCode)
                if(tsbSmall.Checked) {
                    if(ActShip != null) {
                        ActBtn.Checked = false;
                        myField.Ships.Remove(ActShip);
                        myField.Invalidate();
                    }
                    ActBtn = tsbSmall;
                    ActShip = myField.Ships.Add(Ship.ShipTypes_ENUM.Small, new Point());
                    ActShip.Visibility = true;
                }
                else
                    CancelShipAdding();
            else
                CheckedByCode = false;
        }
        #endregion

        #region "Chat"

        private void Chat_SendMsg(string msg) {
            _Com.Write(PRE_CHAT + msg);
            txtChat.Text = DateTime.Now.ToShortTimeString() + "   " + Program.Names[0] + ": " + msg + "\r\n" + txtChat.Text;
        }

        private void Chat_ReceivedMsg(string msg) {
            txtChat.Text = DateTime.Now.ToShortTimeString() + "   " + Program.Names[1] + ": " + msg + "\r\n" + txtChat.Text;
        }


        #endregion

        private void Form1_Resize(object sender, EventArgs e) {
            if(frmWidth != null)
                this.Width = frmWidth;
        }

    }
}